package Push(Push(..), apply, tee, pass, passed,
             buffer, buffered,  qbuffer, qbuffered, q1buffer, q1buffered, pipe,
             (»), sink, spew, regToPush, fifoToPush) where

import FIFO

--@ \subsubsection{Push}
--@
--@ \index{Push@\te{Push} (interface type)|textbf}
--@ The {\mbox{\te{Push a}}} interface represents a stream that
--@ consumes values of type ``\te{a}'' only when ``pushed'' by a producer.
--@
--@ Modules with the Push interface can be combined using the function
--@ \te{pipe},
--@ to model computations that comprise several steps,
--@ each of which may be buffered.

--@
infixr 12 »

--@ \begin{libverbatim}
--@ interface Push #(type a);
--@     method Action push(a x1);
--@ endinterface: Push
--@ \end{libverbatim}
interface Push a =
    push :: a -> Action

--@ Apply a function to the data in the stream.
--@ \begin{libverbatim}
--@ function Push#(a) apply(function b f(a x1), Push#(b) dst);
--@ \end{libverbatim}
apply :: (a -> b) -> Push b -> Push a
apply f dst = interface Push { push x = dst.push (f x) }

--@ Allow an action to peek at the stream.
--@ \begin{libverbatim}
--@ function Push#(a) tee(function Action a(a x1), Push#(a) dst);
--@ \end{libverbatim}
tee :: (a -> Action) -> Push a -> Push a
tee a dst = interface Push { push x = action { a x; dst.push x } }

--@ Wrap the stream in a module (without buffering).
--@ \begin{libverbatim}
--@ module pass#(Push#(a) dst)(Push#(a));
--@ \end{libverbatim}
pass :: (IsModule m c) => Push a -> m (Push a)
pass dst =
    module
      interface
        push x = dst.push x

--@ Apply a function to the data in the stream
--@ and wrap the stream in a module (without buffering).
--@ \begin{libverbatim}
--@ module passed#(function b f(a x1), Push#(b))(Push#(a));
--@ \end{libverbatim}
passed :: (IsModule m c) => (a -> b) -> Push b -> m (Push a)
passed f = pass ∘ apply f

--@ Wrap a stream in a module
--@ (with a register buffer initialized to the first argument).
--@ \begin{libverbatim}
--@ module buffer#(a init, Push#(a) dst)(Push#(a))
--@   provisos (Bits#(a, sa));
--@ \end{libverbatim}
buffer :: (IsModule m c, Bits a sa) => a -> Push a -> m (Push a)
buffer init dst =
     module
       r :: Reg a
       r <- mkReg init
       interface
         push x = action { r := x; dst.push r }

--@ Wrap a stream in a module
--@ (with a FIFO buffer).
--@ \begin{libverbatim}
--@ module qbuffer#(Push#(a) dst)(Push#(a))
--@   provisos (Bits#(a, sa));
--@ \end{libverbatim}
qbuffer :: (IsModule m c, Bits a sa) => Push a -> m (Push a)
qbuffer dst =
    module
      q :: FIFO a <- mkFIFO
      rules
        "push": when True ==> action { dst.push q.first; q.deq }
      interface
        push x = action q.enq x



--@ Wrap a stream in a module
--@ with a 1 element (loopy) FIFO buffer.  This saves register elements, but
--@ the push action logic may depend on checking all elements in the pipe.
--@ \begin{libverbatim}
--@ module qbuffer#(Push#(a) dst)(Push#(a))
--@   provisos (Bits#(a, sa));
--@ \end{libverbatim}
q1buffer :: (IsModule m c, Bits a sa) => Push a -> m (Push a)
q1buffer dst =
    module
      q :: FIFO a <- mkLFIFO
      rules
        "push": when True ==> action { dst.push q.first; q.deq }
      interface
        push x = action q.enq x


--@ Apply a function to the data in the stream
--@ and wrap the stream in a module
--@ (with a register buffer initialized to `\US').
--@ \begin{libverbatim}
--@ module buffered#(function b f(a x1))(Push#(a))
--@   provisos (Bits#(a, sa));
--@ \end{libverbatim}
buffered :: (IsModule m c, Bits a sa) => (a -> b) -> Push b -> m (Push a)
buffered f = buffer _ ∘ apply f

--@ Apply a function to the data in the stream
--@ and wrap the stream in a module
--@ (with a loopy FIFO buffer).
--@ \begin{libverbatim}
--@ module q1buffered#(function b f(a x1))(Push#(a))
--@   provisos (Bits#(a, sa));
--@ \end{libverbatim}
q1buffered :: (IsModule m c, Bits a sa) => (a -> b) -> Push b -> m (Push a)
q1buffered f = q1buffer ∘ apply f

--@ Apply a function to the data in the stream
--@ and wrap the stream in a module
--@ (with a FIFO buffer).
--@ \begin{libverbatim}
--@ module qbuffered#(function b f(a x1))(Push#(a))
--@   provisos (Bits#(a, sa));
--@ \end{libverbatim}
qbuffered :: (IsModule m c, Bits a sa) => (a -> b) -> Push b -> m (Push a)
qbuffered f = qbuffer ∘ apply f


--@ A consumer that drops all data.
--@ \begin{libverbatim}
--@ module sink(Push#(a));
--@ \end{libverbatim}
sink :: (IsModule m c) => m (Push a)
sink =
    module
      interface
        push _ = action {}

--@ A producer that always pushes junk on the given stream.
--@ \begin{libverbatim}
--@ module spew#(Push#(a) dst)(Empty);
--@ \end{libverbatim}
spew :: (IsModule m c) => Push a -> m Empty
spew dst =
    module
      rules "spew": when True ==> dst.push _

--@ Combine two streams (e.g., \te{pipe(spew, sink)}).
--@ \begin{libverbatim}
--@ function m#(b) pipe (function m#(b) f(a x1), m#(a) a)
--@   provisos (Monad#(m));
--@ \end{libverbatim}
(») :: (Monad m) => (a -> m b) -> m a -> m b
f » a = a `bind` f

pipe :: (Monad m) => (a -> m b) -> m a -> m b
pipe f a = (f » a)

--@ Wrap a \te{Push} interface around a register.
--@ \begin{libverbatim}
--@ function Push#(a) regToPush(Reg#(a) r);
--@ \end{libverbatim}
regToPush :: Reg a -> Push a
regToPush r =
    interface Push
      push x = r := x

--@ Wrap a \te{Push} interface around a FIFO.
--@ \begin{libverbatim}
--@ function Push#(a) fifoToPush(FIFO#(a) q);
--@ \end{libverbatim}
fifoToPush :: FIFO a -> Push a
fifoToPush q =
    interface Push
      push x = q.enq x
